import { FC, useCallback, useEffect, useState } from 'react'
import { useFigureModalContext } from '../figure-modal-context'
import { useCurrentProjectFolders } from '../../../hooks/useCurrentProjectFolders'
import { File } from '../../../utils/file'
import { Dashboard, useUppy } from '@uppy/react'
import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
import { Uppy, UppyFile } from '@uppy/core'
import XHRUpload from '@uppy/xhr-upload'
import { refreshProjectMetadata } from '../../../../file-tree/util/api'
import { useProjectContext } from '../../../../../shared/context/project-context'
import Icon from '../../../../../shared/components/icon'
import classNames from 'classnames'
import { Button } from 'react-bootstrap'
import { FileRelocator } from '../file-relocator'
import { useTranslation } from 'react-i18next'
import { useCodeMirrorViewContext } from '../../codemirror-editor'
import { waitForFileTreeUpdate } from '../../../extensions/figure-modal'

const maxFileSize = window.ExposedSettings.maxUploadSize

/* eslint-disable no-unused-vars */
export enum FileUploadStatus {
  ERROR,
  SUCCESS,
  NOT_ATTEMPTED,
  UPLOADING,
}
/* eslint-enable no-unused-vars */

export const FigureModalUploadFileSource: FC = () => {
  const { t } = useTranslation()
  const view = useCodeMirrorViewContext()
  const { dispatch, pastedImageData } = useFigureModalContext()
  const { _id: projectId } = useProjectContext()
  const [, rootFolder] = useCurrentProjectFolders()
  const [folder, setFolder] = useState<File | null>(null)
  const [nameDirty, setNameDirty] = useState<boolean>(false)
  // Files are immutable, so this will point to a (possibly) old version of the file
  const [file, setFile] = useState<UppyFile | null>(null)
  const [name, setName] = useState<string>('')
  const [uploading, setUploading] = useState<boolean>(false)
  const [uploadError, setUploadError] = useState<any>(null)

  const uppy = useUppy(() =>
    new Uppy({
      allowMultipleUploads: false,
      restrictions: {
        maxNumberOfFiles: 1,
        maxFileSize: maxFileSize || null,
        allowedFileTypes: ['image/*', '.pdf'],
      },
      autoProceed: false,
    })
      // use the basic XHR uploader
      .use(XHRUpload, {
        headers: {
          'X-CSRF-TOKEN': window.csrfToken,
        },
        // limit: maxConnections || 1,
        limit: 1,
        fieldName: 'qqfile', // "qqfile" field inherited from FineUploader
      })
  )

  const dispatchUploadAction = useCallback(
    (name?: string, file?: UppyFile | null, folder?: File | null) => {
      if (!name || !file) {
        dispatch({ getPath: undefined })
        return
      }
      dispatch({
        getPath: async () => {
          const fileTreeUpdate = waitForFileTreeUpdate(view)
          const uploadResult = await uppy.upload()
          await fileTreeUpdate.withTimeout(500)
          if (!uploadResult.successful) {
            throw new Error('Upload failed')
          }
          const uploadFolder = folder ?? rootFolder
          return uploadFolder.path === '' && uploadFolder.name === 'rootFolder'
            ? `${name}`
            : `${uploadFolder.path ? uploadFolder.path + '/' : ''}${
                uploadFolder.name
              }/${name}`
        },
      })
    },
    [dispatch, rootFolder, uppy, view]
  )

  useEffect(() => {
    // broadcast doc metadata after each successful upload
    const onUploadSuccess = (_file: UppyFile, response: any) => {
      setUploading(false)
      if (response.body.entity_type === 'doc') {
        window.setTimeout(() => {
          refreshProjectMetadata(projectId, response.body.entity_id)
        }, 250)
      }
    }

    const onFileAdded = (file: UppyFile) => {
      const newName = nameDirty ? name : file.name
      setName(newName)
      setFile(file)
      dispatchUploadAction(newName, file, folder)
    }

    const onFileRemoved = () => {
      if (!nameDirty) {
        setName('')
      }
      setFile(null)
      dispatchUploadAction(undefined, null, folder)
    }

    const onUpload = () => {
      // Set endpoint dynamically https://github.com/transloadit/uppy/issues/1790#issuecomment-581402293
      setUploadError(null)
      uppy.getFiles().forEach(file => {
        uppy.setFileState(file.id, {
          // HACK: There seems to be no other way of renaming the underlying file object
          data: new globalThis.File([file.data], name),
          meta: {
            ...file.meta,
            name,
          },
          name,
          xhrUpload: {
            ...(file as any).xhrUpload,
            endpoint: `/project/${projectId}/upload?folder_id=${
              (folder ?? rootFolder).id
            }`,
          },
        })
      })
      setUploading(true)
    }

    // handle upload errors
    const onError = (_file: UppyFile, error: any, response: any) => {
      setUploading(false)
      setUploadError(error)
      switch (response?.status) {
        case 429:
          dispatch({
            error: 'Unable to process your file. Please try again later.',
          })
          break

        case 403:
          dispatch({ error: 'Your session has expired' })
          break

        default:
          dispatch({
            error: response?.body?.error ?? 'An unknown error occured',
          })
          break
      }
    }

    uppy
      .on('file-added', onFileAdded)
      .on('file-removed', onFileRemoved)
      .on('upload-success', onUploadSuccess)
      .on('upload', onUpload)
      .on('upload-error', onError)

    return () => {
      uppy
        .off('file-added', onFileAdded)
        .off('file-removed', onFileRemoved)
        .off('upload-success', onUploadSuccess)
        .off('upload', onUpload)
        .off('upload-error', onError)
    }
  }, [
    uppy,
    folder,
    rootFolder,
    name,
    nameDirty,
    dispatchUploadAction,
    projectId,
    file,
    dispatch,
  ])

  useEffect(() => {
    if (pastedImageData) {
      uppy.addFile(pastedImageData)
    }
  }, [uppy, pastedImageData])

  return (
    <>
      <div className="figure-modal-upload">
        {file ? (
          <FileContainer
            name={file.name}
            size={file.size}
            status={
              uploading
                ? FileUploadStatus.UPLOADING
                : uploadError
                ? FileUploadStatus.ERROR
                : FileUploadStatus.NOT_ATTEMPTED
            }
            onDelete={() => {
              uppy.removeFile(file.id)
              setFile(null)
              const newName = nameDirty ? name : ''
              setName(newName)
              dispatchUploadAction(newName, null, folder)
            }}
          />
        ) : (
          <Dashboard
            uppy={uppy}
            showProgressDetails
            height={120}
            width="100%"
            showLinkToFileUploadResult={false}
            proudlyDisplayPoweredByUppy={false}
            showSelectedFiles={false}
            hideUploadButton
            locale={{
              strings: {
                // Text to show on the droppable area.
                // `%{browseFiles}` is replaced with a link that opens the system file selection dialog.
                dropPasteFiles: `${t(
                  'drag_here_paste_an_image_or'
                )} %{browseFiles}`,
                // Used as the label for the link that opens the system file selection dialog.
                browseFiles: t('select_from_your_computer'),
              },
            }}
          />
        )}
      </div>
      <FileRelocator
        folder={folder}
        name={name}
        nameDisabled={!file && !nameDirty}
        onFolderChanged={item =>
          dispatchUploadAction(name, file, item ?? rootFolder)
        }
        onNameChanged={name => dispatchUploadAction(name, file, folder)}
        setFolder={setFolder}
        setName={setName}
        setNameDirty={setNameDirty}
      />
    </>
  )
}

export const FileContainer: FC<{
  name: string
  size?: number
  status: FileUploadStatus
  onDelete?: () => any
}> = ({ name, size, status, onDelete }) => {
  const { t } = useTranslation()
  let icon
  switch (status) {
    case FileUploadStatus.ERROR:
      icon = 'times-circle'
      break
    case FileUploadStatus.SUCCESS:
      icon = 'check-circle'
      break
    case FileUploadStatus.NOT_ATTEMPTED:
      icon = 'picture-o'
      break
    case FileUploadStatus.UPLOADING:
      icon = 'spinner'
  }
  return (
    <div className="file-container">
      <div className="file-container-file">
        <Icon
          spin={status === FileUploadStatus.UPLOADING}
          type={icon}
          className={classNames(
            {
              'text-success': status === FileUploadStatus.SUCCESS,
              'text-danger': status === FileUploadStatus.ERROR,
            },
            'file-icon'
          )}
        />
        <div className="file-info">
          <span className="file-name" aria-label={t('file_name_figure_modal')}>
            {name}
          </span>
          {size !== undefined && (
            <FileSize size={size} className="text-small" />
          )}
        </div>
        <Button
          bsStyle={null}
          className="btn btn-link p-0"
          aria-label={t('remove_or_replace_figure')}
          onClick={() => onDelete && onDelete()}
        >
          <Icon fw type="times-circle" className="file-action file-icon" />
        </Button>
      </div>
    </div>
  )
}

const FileSize: FC<{ size: number; className?: string }> = ({
  size,
  className,
}) => {
  const { t } = useTranslation()
  const BYTE_UNITS: [string, number][] = [
    ['B', 1],
    ['KB', 1e3],
    ['MB', 1e6],
    ['GB', 1e9],
    ['TB', 1e12],
    ['PB', 1e15],
  ]
  const labelIndex = Math.min(
    Math.floor(Math.log10(size) / 3),
    BYTE_UNITS.length - 1
  )

  const [label, bytesPerUnit] = BYTE_UNITS[labelIndex]
  const sizeInUnits = Math.round(size / bytesPerUnit)
  return (
    <span aria-label={t('file_size')} className={className}>
      {sizeInUnits} {label}
    </span>
  )
}
